﻿using System;
using System.Configuration;
using System.Linq;
using System.ServiceModel.Description;
using XrmLibrary.Adapters;
using XrmLibrary.ConfigFile;
using XrmLibrary.Connections;
using XrmLibrary.Context;
using XrmLibrary.Exceptions;
using XrmLibrary.MessageStrings;

namespace XrmLibrary.Configurations
{
    internal sealed class CrmConfiguration : CrmConfigurationBase
    {
        private AuthenticationType _authenticationType;
        private CrmConnectionManager _crmConnectionManager;
        private CrmConnectionManager.Configuration _crmServerConfiguration;
        private Uri _crmOrganizationUri;
        private Uri _crmDiscoveryUri;
        private CrmServiceConfigurationElement _crmServiceConfiguration;

        public override void Load()
        {
            _crmServiceConfiguration = AdapterConfiguration.GetCrmServiceConfiguration();
            _crmConnectionManager = new CrmConnectionManager();
            _crmOrganizationUri = new Uri(_crmServiceConfiguration.CrmOrganizationUrl);
            _authenticationType = GetAuthenticationType(_crmServiceConfiguration.CrmAuthenticationType);
            if(!String.IsNullOrEmpty(_crmServiceConfiguration.CrmDiscoveryUrl))
            {
                _crmDiscoveryUri = new Uri(_crmServiceConfiguration.CrmDiscoveryUrl);
                IsCrmDiscoveryServiceConfigured = true;
            }
            else
            {
                IsCrmDiscoveryServiceConfigured = false;
            }
            _crmServerConfiguration = GetCrmServerConfiguration(_crmServiceConfiguration.CrmUserName, _crmServiceConfiguration.CrmPassword);
        }

        public override String GetConfigurationName()
        {
            return AdapterConfiguration.GetConfigurationName();
        }

        public override Boolean IsCrmDiscoveryServiceConfigured { get; protected set; }

        /// <summary>
        /// Gets the type of the authentication.
        /// </summary>
        /// <returns>The authentication type selected in the config file.</returns>
        private static AuthenticationType GetAuthenticationType(String authType)
        {
            switch (authType.ToUpper())
            {
                case "PASSPORT":
                    return AuthenticationType.Passport;
                case "AD":
                    return AuthenticationType.AD;
                case "IFD":
                    return AuthenticationType.IFD;
                default:
                    String error = null;
                    if (String.IsNullOrEmpty(authType))
                    {
                        error = ErrorStrings.ConfigSettingCrmAuthenticationTypeIsNullOrEmpty;
                        XrmLibraryException exception = new XrmLibraryException(ErrorStrings.ConfigSettingCrmAuthenticationTypeIsNullOrEmpty);
                        Tracer.Log(LogContext.InstantiateFatal(format: error, exception: exception));
                        throw exception;
                    }
                    error = String.Format(ErrorStrings.ConfigSettingCrmAuthenticationTypeIsInvalid, authType);
                    Tracer.Log(LogContext.InstantiateFatal(format: error));
                    throw new XrmLibraryException(error);
            }
        }


        /// <summary>
        /// Gets the configuration for the given authentication type.
        /// </summary>
        /// <param name="userName">Name of the user.</param>
        /// <param name="password">The password.</param>
        /// <returns>
        /// The configuration.
        /// </returns>
        private CrmConnectionManager.Configuration GetCrmServerConfiguration(String userName, String password)
        {
            ValidateUserName(userName);
            ValidatePassword(password);
            ClientCredentials credentials = new ClientCredentials();
            if(_authenticationType == AuthenticationType.AD)
            {
                String[] names = userName.Split('\\');
                credentials.Windows.ClientCredential = new System.Net.NetworkCredential(names[1], password, names[0]);
            }
            else
            {
                credentials.UserName.UserName = userName;
                credentials.UserName.Password = password;
            }

            switch (_authenticationType)
            {
                case AuthenticationType.Passport:
                    return _crmConnectionManager.GetServerConfiguration(_crmOrganizationUri.OriginalString, null, credentials, GetClientCredentialsFromDeviceIdManager());
                case AuthenticationType.AD:
                case AuthenticationType.IFD:
                    return _crmConnectionManager.GetServerConfiguration(_crmOrganizationUri.OriginalString, null, credentials, null);
                default:
                    Tracer.Log(LogContext.InstantiateFatal(format:ErrorStrings.AuthenticationTypeDoesNotSupportConfigurationCreation));
                    throw new XrmLibraryException(ErrorStrings.AuthenticationTypeDoesNotSupportConfigurationCreation);
            }
        }

        private ClientCredentials GetClientCredentialsFromDeviceIdManager()
        {
            if(!String.IsNullOrEmpty(_crmServiceConfiguration.DeviceName))
            {
                return DeviceIdManager.LoadOrRegisterDevice(_crmServiceConfiguration.DeviceName, _crmServiceConfiguration.DevicePassword);
            }
            else
            {
                return DeviceIdManager.LoadOrRegisterDevice();
            }
        }

        private void ValidatePassword(string password)
        {
            if (String.IsNullOrEmpty(password))
            {
                String error = String.Format(ErrorStrings.ConfigSettingPasswordIsNullOrEmpty, _authenticationType);
                Tracer.Log(LogContext.InstantiateFatal(format:error));
                throw new XrmLibraryException(error);
            }
        }

        private void ValidateUserName(string userName)
        {
            if (String.IsNullOrEmpty(userName))
            {
                String error = String.Format(ErrorStrings.ConfigSettingCrmUserNameIsNullOrEmpty, _authenticationType);
                Tracer.Log(LogContext.InstantiateFatal(format:error));
                throw new XrmLibraryException(error);
            }
            if(_authenticationType == AuthenticationType.IFD && !userName.Contains('\\'))
            {
                String error = String.Format(ErrorStrings.ConfigSettingCrmUserNameDoesNotContainDomain, _authenticationType);
                Tracer.Log(LogContext.InstantiateFatal(format:error));
                throw new XrmLibraryException(error);
            }
        }

        public override IOrganizationServiceAdapter CreateOrganizationServiceAdapter()
        {
            switch (_authenticationType)
            {
                case AuthenticationType.Passport:
                    return new OrganizationServiceAdapter(_crmOrganizationUri, null, _crmServerConfiguration.Credentials, _crmServerConfiguration.DeviceCredentials);
                case AuthenticationType.AD:
                case AuthenticationType.IFD:
                    return new OrganizationServiceAdapter(_crmOrganizationUri, null, _crmServerConfiguration.Credentials, null);
                default:
                    throw new XrmLibraryException(ErrorStrings.AuthenticationTypeDoesNotSupportOrganizationServiceProxyCreation);
            }
        }

        public override IDiscoveryServiceAdapter CreateDiscoveryServiceAdapter()
        {
            switch (_authenticationType)
            {
                case AuthenticationType.Passport:
                    return new DiscoveryServiceAdapter(_crmDiscoveryUri, null, _crmServerConfiguration.Credentials,_crmServerConfiguration.DeviceCredentials);
                case AuthenticationType.AD:
                case AuthenticationType.IFD:
                    return new DiscoveryServiceAdapter(_crmDiscoveryUri, null, _crmServerConfiguration.Credentials, null);
                default:
                    throw new XrmLibraryException(ErrorStrings.AuthenticationTypeDoesNotSupportDiscoveryServiceProxyCreation);
            }
        }

        public override void Reinitialize(ClientCredentials credentials)
        {
            _crmServerConfiguration = _crmConnectionManager.GetServerConfiguration(_crmOrganizationUri.OriginalString, null, credentials, DeviceIdManager.LoadOrRegisterDevice());
        }
    }
}
